/*
 * Copyright (C) 2011 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package vogar.tasks;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Collections;
import java.util.HashSet;
import java.util.Properties;
import java.util.Set;
import java.util.regex.Pattern;
import vogar.Action;
import vogar.Classpath;
import vogar.Driver;
import vogar.Mode;
import vogar.Outcome;
import vogar.Result;
import vogar.Run;
import vogar.TestProperties;
import vogar.commands.Command;
import vogar.commands.CommandFailedException;
import vogar.commands.Javac;

/**
 * Compiles classes for the given action and makes them ready for execution.
 */
public final class BuildActionTask extends Task {
    private static final Pattern JAVA_SOURCE_PATTERN = Pattern.compile("\\/(\\w)+\\.java$");

    private final Action action;
    private final Run run;
    private final Driver driver;
    private final File jar;

    public BuildActionTask(Run run, Action action, Driver driver, File jar) {
        super("build " + action.getName());
        this.run = run;
        this.action = action;
        this.driver = driver;
        this.jar = jar;
    }

    @Override protected Result execute() throws Exception {
        try {
            compile(action, jar);
            return Result.SUCCESS;
        } catch (CommandFailedException e) {
            driver.addEarlyResult(new Outcome(action.getName(), Result.COMPILE_FAILED,
                    e.getOutputLines()));
            return Result.COMPILE_FAILED;
        } catch (IOException e) {
            driver.addEarlyResult(new Outcome(action.getName(), Result.ERROR, e));
            return Result.ERROR;
        }
    }

    /**
     * Returns the .jar file containing the action's compiled classes.
     *
     * @throws CommandFailedException if javac fails
     */
    private void compile(Action action, File jar) throws IOException {
        File classesDir = run.localFile(action, "classes");
        run.mkdir.mkdirs(classesDir);
        createJarMetadataFiles(action, classesDir);

        Set<File> sourceFiles = new HashSet<File>();
        File javaFile = action.getJavaFile();
        Javac javac = new Javac(run.log, run.javaPath("javac"));
        if (run.debugPort != null) {
            javac.debug();
        }
        if (javaFile != null) {
            if (!JAVA_SOURCE_PATTERN.matcher(javaFile.toString()).find()) {
                throw new CommandFailedException(Collections.<String>emptyList(),
                        Collections.singletonList("Cannot compile: " + javaFile));
            }
            sourceFiles.add(javaFile);
            Classpath sourceDirs = Classpath.of(action.getSourcePath());
            sourceDirs.addAll(run.sourcepath);
            javac.sourcepath(sourceDirs.getElements());
        }
        if (!sourceFiles.isEmpty()) {
            if (!run.buildClasspath.isEmpty()) {
                javac.bootClasspath(run.buildClasspath);
            }
            javac.classpath(run.classpath)
                    .destination(classesDir)
                    .extra(run.javacArgs)
                    .compile(sourceFiles);
        }

        new Command(run.log, run.javaPath("jar"), "cvfM", jar.getPath(),
                "-C", classesDir.getPath(), "./").execute();
    }

    /**
     * Writes files to {@code classesDir} to be included in the .jar file for
     * {@code action}.
     */
    private void createJarMetadataFiles(Action action, File classesDir) throws IOException {
        OutputStream propertiesOut
                = new FileOutputStream(new File(classesDir, TestProperties.FILE));
        Properties properties = new Properties();
        fillInProperties(properties, action);
        properties.store(propertiesOut, "generated by " + Mode.class.getName());
        propertiesOut.close();
    }

    /**
     * Fill in properties for running in this mode
     */
    private void fillInProperties(Properties properties, Action action) {
        properties.setProperty(TestProperties.TEST_CLASS_OR_PACKAGE, action.getTargetClass());
        properties.setProperty(TestProperties.QUALIFIED_NAME, action.getName());
        properties.setProperty(TestProperties.MONITOR_PORT, Integer.toString(run.firstMonitorPort));
        properties.setProperty(TestProperties.TIMEOUT, Integer.toString(run.timeoutSeconds));
        properties.setProperty(TestProperties.PROFILE, Boolean.toString(run.profile));
        properties.setProperty(TestProperties.PROFILE_DEPTH, Integer.toString(run.profileDepth));
        properties.setProperty(TestProperties.PROFILE_INTERVAL,
                Integer.toString(run.profileInterval));
        properties.setProperty(TestProperties.PROFILE_FILE, run.profileFile.getName());
        properties.setProperty(TestProperties.PROFILE_THREAD_GROUP,
                Boolean.toString(run.profileThreadGroup));
        properties.setProperty(TestProperties.TEST_ONLY, Boolean.toString(run.testOnly));
    }
}
